package org.example.demoschedules.service;

import lombok.extern.slf4j.Slf4j;
import org.example.demoschedules.model.OrderedMap;

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

/**
 * 半天班排班，指同一拨人安排到上午和下午两个班中。
 * 规则：次日的上午优先从上一日的下午人员开始排
 *
 * @author 单红宇
 * @since 2024/10/15 8:24
 */
@Slf4j
public class ScheduleByHalf {

    /**
     * 半天班排班
     *
     * @param members          用户集合
     * @param amCount          上午排班人数
     * @param pmCount          下午排班人数
     * @param startDate        开始日期
     * @param days             排班天数
     * @param nonWorkingDays   完全不排班的日期
     * @param previousSchedule 上一日排班（用于衔接排班，不需要衔接时为空即可）
     * @return HalfSchedule
     */
    public OrderedMap<LocalDate, HalfSchedule> generateSchedule(List<String> members, int amCount, int pmCount, LocalDate startDate, int days,
                                                                HalfSchedule previousSchedule, Set<LocalDate> nonWorkingDays) {
        OrderedMap<LocalDate, HalfSchedule> halfSchedules = new OrderedMap<>();
        HalfSchedule lastSchedule = previousSchedule;
        if (previousSchedule == null) {
            lastSchedule = new HalfSchedule(startDate.minusDays(1));
        }
        // key为上一日的日期，和真实传入的previousSchedule中的日期对应与否没有关系，因为不会实际使用到previousSchedule中的日期
        halfSchedules.put(lastSchedule.getDate(), lastSchedule);

        for (int i = 0; i < days; i++) {
            LocalDate date = startDate.plusDays(i);
            if (nonWorkingDays.contains(date)) {
                continue;
            }
            halfSchedules.put(date, this.generateDaySchedule(members, amCount, pmCount, halfSchedules.get(halfSchedules.lastKey()), date));
        }

        // 移除开始添加的previousSchedule
        halfSchedules.remove(halfSchedules.firstKey());
        return halfSchedules;
    }

    /**
     * 为某一日排班
     * <p>
     * 规则要点如下：
     * 1、参数成员集合，包含N人
     * 2、参数上午班人数和参数下午班人数，均不能超过用户集合
     * 3、下午班的人数如果不指定则人数为总人数减去上午班人数
     * 4、当上午班人数小于总人数时，次日排班时，从前一日排班的下午第一个人开始排，排到集合最后一个人后，再从集合第一个人开始。依此类推排完上午班和下午班。
     * 5、排班状态使用 WorkStatus，里面包含 WORK 和 REST
     * 6、参数前一日排班数据，如果该参数不为空则衔接该日排班；如果为空则从成员集合第一个用户开始排。
     * 7、参数不需要排班的日期集合，遇到集合中的日期时，跳过该日期
     * 8、参数排班开始日期
     * 9、参数排班周数
     * 10、定义一个Java类 HalfSchedule 表示半天班，里面有2个SortedMap集合，一个是上午班容器，一个是下午班容器。
     * 11、最终排班结果和前一日排班为使用 HalfSchedule 对象
     * </p>
     *
     * @param amCount         上午排班人数
     * @param pmCount         下午排班人数
     * @param members         用户集合
     * @param prevDaySchedule 上一日排班
     * @param date            日期
     * @return HalfSchedule
     */
    public HalfSchedule generateDaySchedule(List<String> members, int amCount, int pmCount, HalfSchedule prevDaySchedule, LocalDate date) {
        if (amCount > members.size() || pmCount > members.size()) {
            throw new IllegalArgumentException("上午班人数或下午排班人数均不能超过成员总数");
        }
        HalfSchedule halfSchedule = new HalfSchedule(date);
        // 创建一个2倍长度的用户集合来模拟圆环
        List<String> circularMembers = new ArrayList<>(members);
        circularMembers.addAll(members);

        // 从上一日下午排班中获取第一个存在于用户集合中的用户，这个用户作为今日排班的第一个用户
        String startAmUser;
        if (prevDaySchedule != null) {
            startAmUser = prevDaySchedule.getPmSchedule().keySet().stream().filter(members::contains).findFirst().orElseGet(() -> members.get(0));
        } else {
            startAmUser = members.get(0);
        }
        int startIndex = circularMembers.indexOf(startAmUser);

        // 填充上班的用户
        for (int i = 0; i < amCount + pmCount; i++) {
            if (i < amCount) {
                halfSchedule.getAmSchedule().put(circularMembers.get(startIndex + i), WorkStatus.WORK);
            } else {
                halfSchedule.getPmSchedule().put(circularMembers.get(startIndex + i), WorkStatus.WORK);
            }
        }

        return halfSchedule;
    }

    /**
     * 打印排班表
     *
     * @param allHalfSchedule allHalfSchedule
     */
    public void printSchedule(OrderedMap<LocalDate, HalfSchedule> allHalfSchedule) {
        allHalfSchedule.entrySet().forEach(entry -> {
            log.info("日期: {}", entry.getKey());
            log.info("上午班: {}", entry.getValue().getAmSchedule());
            log.info("下午班: {}\n", entry.getValue().getPmSchedule());
        });
    }

}
